//! Mock types and functions to generate GethData used for tests

use eth_types::{address, bytecode, bytecode::Bytecode, word, Address, Bytes, Word};
use ethers_signers::LocalWallet;
use rand::SeedableRng;
use rand_chacha::ChaCha20Rng;
use std::sync::LazyLock;
mod account;
mod block;
pub mod test_ctx;
mod transaction;

pub(crate) use account::MockAccount;
pub(crate) use block::MockBlock;
pub use test_ctx::TestContext;
pub use transaction::{AddrOrWallet, MockTransaction, CORRECT_MOCK_TXS};

/// Mock block gas limit
pub const MOCK_BLOCK_GAS_LIMIT: u64 = 10_000_000_000_000_000;

/// Mock 1 ETH
pub static MOCK_1_ETH: LazyLock<Word> = LazyLock::new(|| eth(1));
/// Mock coinbase value
pub static MOCK_COINBASE: LazyLock<Address> =
    LazyLock::new(|| address!("0x00000000000000000000000000000000c014ba5e"));
/// Mock gasprice value
pub static MOCK_GASPRICE: LazyLock<Word> = LazyLock::new(|| Word::from(1u8));
/// Mock BASEFEE value
pub static MOCK_BASEFEE: LazyLock<Word> = LazyLock::new(Word::zero);
/// Mock GASLIMIT value
pub static MOCK_GASLIMIT: LazyLock<Word> = LazyLock::new(|| Word::from(0x2386f26fc10000u64));
/// Mock chain ID value
pub static MOCK_CHAIN_ID: u64 = 1338;
/// Mock DIFFICULTY value
pub static MOCK_DIFFICULTY: LazyLock<Word> = LazyLock::new(|| Word::from(0x200000u64));
/// Mock DIFFICULTY value for l2geth (always 0)
pub static MOCK_DIFFICULTY_L2GETH: LazyLock<Word> = LazyLock::new(|| Word::from(0x0u64));
/// Mock accounts loaded with ETH to use for test cases.
pub static MOCK_ACCOUNTS: LazyLock<Vec<Address>> = LazyLock::new(|| {
    vec![
        address!("0x000000000000000000000000000000000cafe111"),
        address!("0x000000000000000000000000000000000cafe222"),
        address!("0x000000000000000000000000000000000cafe333"),
        address!("0x000000000000000000000000000000000cafe444"),
        address!("0x000000000000000000000000000000000cafe555"),
    ]
});
/// Mock EVM codes to use for test cases.
pub static MOCK_CODES: LazyLock<Vec<Bytes>> = LazyLock::new(|| {
    vec![
        Bytes::from([0x60, 0x10, 0x00]), // PUSH1(0x10), STOP
        Bytes::from([0x60, 0x01, 0x60, 0x02, 0x01, 0x00]), // PUSH1(1), PUSH1(2), ADD, STOP
        Bytes::from([0x60, 0x01, 0x60, 0x02, 0x02, 0x00]), // PUSH1(1), PUSH1(2), MUL, STOP
        Bytes::from([0x60, 0x02, 0x60, 0x01, 0x03, 0x00]), // PUSH1(2), PUSH1(1), SUB, STOP
        Bytes::from([0x60, 0x09, 0x60, 0x03, 0x04, 0x00]), // PUSH1(9), PUSH1(3), DIV, STOP
        Bytes::from([0x30; 256]),        // ADDRESS * 256
    ]
});
/// Mock wallets used to generate correctly signed and hashed Transactions.
pub static MOCK_WALLETS: LazyLock<Vec<LocalWallet>> = LazyLock::new(|| {
    let mut rng = ChaCha20Rng::seed_from_u64(2u64);
    vec![
        LocalWallet::new(&mut rng),
        LocalWallet::new(&mut rng),
        LocalWallet::new(&mut rng),
    ]
});
/// Mock EVM bytecode for a deployed contract.
/// PUSH1 0x20
/// PUSH1 0
/// PUSH1 0
/// CALLDATACOPY
/// PUSH1 0x20
/// PUSH1 0
/// RETURN
///
/// bytecode: 0x6020600060003760206000F3
///
/// // constructor
/// PUSH12 0x6020600060003760206000F3
/// PUSH1 0
/// MSTORE
/// PUSH1 0xC
/// PUSH1 0x14
/// RETURN
///
/// bytecode: 0x6B6020600060003760206000F3600052600C6014F3
pub static MOCK_DEPLOYED_CONTRACT_BYTECODE: LazyLock<Word> =
    LazyLock::new(|| word!("6B6020600060003760206000F3600052600C6014F3"));

/// Generate a [`Word`] which corresponds to a certain amount of ETH.
pub fn eth(x: u64) -> Word {
    Word::from(x) * Word::from(10u64.pow(18))
}

/// Express an amount of ETH in GWei.
pub fn gwei(x: u64) -> Word {
    Word::from(x) * Word::from(10u64.pow(9))
}

/// Holds the parameters for generating mock EVM bytecode for a contract call
pub struct MockCallBytecodeParams {
    /// The address to call with the generated bytecode
    pub address: Address,
    /// The data to be passed as arguments to the contract function.
    pub pushdata: Vec<u8>,
    /// The offset in memory where the return data will be stored.
    pub return_data_offset: usize,
    /// The size of the return data.
    pub return_data_size: usize,
    /// The length of the call data.
    pub call_data_length: usize,
    /// The offset in memory where the call data will be stored.
    pub call_data_offset: usize,
    /// The amount of gas to be used for the contract call.
    pub gas: u64,
    /// The instructions to be executed after the contract call.
    pub instructions_after_call: Bytecode,
}

/// Set default parameters for MockCallBytecodeParams
impl Default for MockCallBytecodeParams {
    fn default() -> Self {
        MockCallBytecodeParams {
            address: address!("0x0000000000000000000000000000000000000000"),
            pushdata: Vec::new(),
            return_data_offset: 0x00usize,
            return_data_size: 0x00usize,
            call_data_length: 0x00usize,
            call_data_offset: 0x00usize,
            gas: 0x1_0000u64,
            instructions_after_call: Bytecode::default(),
        }
    }
}

/// Generate mock EVM bytecode that performs a contract call
pub fn generate_mock_call_bytecode(params: MockCallBytecodeParams) -> Bytecode {
    bytecode! {
        .op_mstore(
            0u64,
            Word::from_big_endian(&params.pushdata)
        )
        .op_call(
            params.gas,
            params.address,
            0u64,
            params.call_data_offset,
            params.call_data_length,
            params.return_data_size,
            params.return_data_offset,
        )
        .append(&params.instructions_after_call)
        STOP
    }
}
